package maquina_virtualP1.maquinap;

import java.util.Stack;
import java.util.ArrayList;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

/**
 * Clase que implementa la Maquina P
 * @author Miguel Angel Diaz
 * @author David Martin
 * @version 1.0
 */
public class MaquinaP {	
	
	// Pila
	private Stack<Integer> pila;
	
	//Memoria de datos
	private ArrayList<Integer> mDatos;
	
	//Memoria de instrucciones
	private ArrayList<String> mInstr;
	
	// Contador de programa
	private int cp;
	
	// Registro de estado: 'r' ejecutando; 's' parada y 'e' error
	private char f;
	
	// Mensaje de informacion
	private String info;
	
	/**
	 * Inicializa todos los atributos del objeto
	 */
	public MaquinaP(){
		reinicia();
		mInstr = new ArrayList<String>();
	}
	
	/**
	 * Inicializa todos los atributos del objeto menos la memoria de instrucciones, que no la cambia
	 */
	public void reinicia(){
		pila = new Stack<Integer>();
		mDatos = new ArrayList<Integer>();
		cp = 0;
		f = 'r';
		info = "";
	}
	
	/**
	 * Consulta el cp
	 * @return cp
	 */
	public int getCP() {
		return cp;
	}

	/**
	 * Modifica el cp
	 * @param cp
	 */
	public void setCP(int cp) {
		this.cp = cp;
	}

	/**
	 * Consulta el registro de estado
	 * @return caracter con el estado
	 */
	public char getF() {
		return f;
	}

	/**
	 * Modifica el registro de estado
 	 * @param f caracter con el estado
	 */
	public void setF(char f) {
		this.f = f;
	}

	/**
	 * Consulta la cima de la pila
	 * @return cima de la pila
	 */
	public int getCimaPila() {
		if (!pila.empty())
			return pila.peek();
		else
			return Integer.MIN_VALUE;
	}

	/**
	 * Consulta la pila
	 * @return la pila
	 */
	public Stack<Integer> getPila() {
		return pila;
	}

	/**
	 * Consulta una posicion de memoria de datos
	 * Si no esta inicializada, devuelve Integer.MIN_VALUE
	 * @param pos 
	 * @return contenido de la posicion pos
	 */
	public int getPosMDatos(int pos) {
		if (pos<mDatos.size() && mDatos.get(pos)!=null)
			return mDatos.get(pos);
		else
			return Integer.MIN_VALUE;
	}

	/**
	 * Consulta la memoria de datos
	 * @return contenido memoria de datos
	 */
	public ArrayList<Integer> getMDatos() {
		return mDatos;
	}

	/**
	 * Consulta una posicion de memoria de instrucciones
	 * @param pos
	 * @return contenido de la posicion pos
	 */
	public String getPosMInstr(int pos) {
		if (pos<mInstr.size())
			return mInstr.get(pos);
		else
			return null;
	}

	/**
	 * Consulta la memoria de instrucciones
	 * @return memoria de instrucciones
	 */
	public ArrayList<String> getMInstr() {
		return mInstr;
	}

	/**
	 * Cambia la memoria de instrucciones
	 * @param instrucciones Nueva memoria de instrucciones
	 */
	public void setPrograma(ArrayList<String> instrucciones){
		mInstr = instrucciones;
	}
	
	/**
	 * Lee un fichero escrito en  codigo objeto
	 * @param rutaFichero
	 */
	public void cargarPrograma(String rutaFichero){
		File fich= new File(rutaFichero);
		FileReader fichero;
		try{
			fichero = new FileReader(fich);
			mInstr = new ArrayList<String>();
			BufferedReader entrada = null;
		    try {
		      entrada = new BufferedReader(fichero);
		      String linea = null;
		      while ((linea = entrada.readLine()) != null){
		    	  String leido = linea.trim();
		    	  mInstr.add(leido.substring(0, leido.length()-1));
		      }
		    }
		    catch (Exception ex) {
			      setError("Error al leer el archivo: " + rutaFichero);
			}
		}
		catch(java.io.FileNotFoundException e) {
			setError("Fichero no valido: " + rutaFichero);
		}
		
	}
	
	/**
	 * Consulta si la Maquina P tiene un programa cargado en memoria
	 * @return cierto si tiene un programa cargado
	 */
	public boolean programaCargado(){
		return mInstr.size() > 0;
	}
	
	/**
	 * Recupera el parametro de la instruccion
	 * @param instr Instruccion
	 * @return parametro
	 */
	private int getParam(String instr){
		int pAp = instr.indexOf('(');
		int pCi = instr.indexOf(')');
		try {
			if ((pAp < pCi-1) && (pCi == instr.length()-1))
				return Integer.valueOf(instr.substring(pAp+1,pCi));
			else 
				setInfo("Instruccion incorrecta: " + instr);
		}
		catch (NumberFormatException e){
			setError("Parametro incorrecto: " + instr.substring(pAp+1,pCi));
		}
		return Integer.MIN_VALUE;
	}
	
	/**
	 * Ejecuta la siguiente instruccion si se puede
	 */
	public void ejecutaInstruccion(){
		if (puedeContinuar()){
			String sig = mInstr.get(cp);
			if (sig.startsWith("apila(") && sig.endsWith(")")){
		    	apila(getParam(sig));
		    }
		    else if (sig.startsWith("apila-dir(") && sig.endsWith(")")){
		    	apilaDir(getParam(sig));
		    }
		    else if (sig.startsWith("desapila-dir(") && sig.endsWith(")")){
		    	desapilaDir(getParam(sig));
		    }
		    else if (sig.equals("suma"))
		    	suma();
		    else if (sig.equals("resta"))
		    	resta();
		    else if (sig.equals("mult"))
		    	mult();
		    else if (sig.equals("div"))
		    	div();
		    else if (sig.equals("mod"))
		    	mod();
		    else if (sig.equals("inverso"))
		    	inverso();
		    else if (sig.equals("or"))
		    	or();
		    else if (sig.equals("and"))
		    	and();
		    else if (sig.equals("igual"))
		    	igual();
		    else if (sig.equals("dist"))
		    	dist();
		    else if (sig.equals("menor"))
		    	menor();
		    else if (sig.equals("menigual"))
		    	menigual();
		    else if (sig.equals("mayor"))
		    	mayor();
		    else if (sig.equals("mayigual"))
		    	mayigual();
		    else if (sig.equals("neg"))
		    	neg();
		    else 
		    	setInfo("Operacion desconocida: " + sig);
		}
	}

	/**
	 * Ejecuta el programa desde la posicion actual
	 */
	public void ejecutaPrograma(){
		while (puedeContinuar())
			ejecutaInstruccion();
	}
	
	/**
	 * Consulta si se puede continuar con la ejecucion: si el estado es ejecutando ('r') y hay mas instrucciones por ejecutar
	 * @return cierto si se puede seguir
	 */
	private boolean puedeContinuar(){
		if (f == 'r'){
			if (cp < mInstr.size())
				return true;
			else 
				setInfo("Programa ejecutado correctamente");
		}
		return false;		
	}
	
	/**
	 * Consulta el mensaje de error
	 * @return error
	 */
	public String getInfo(){
		return info;
	}
	
	/**
	 * Modifica el estado a error y guarda el mensaje
	 * @param error Mensaje de error
	 */
	public void setError(String error){
		f = 'e';
		this.info = "ERROR: " + error;
	}
	
	/**
	 * Modifica el estado a parada y guarda el mensaje
	 * @param info Mensaje de informacion
	 */
	public void setInfo(String info){
		f = 's';
		this.info = "INFO: " + info;
	}
	
	/**
	 * Apila el parametro
	 * @param n
	 */
	private void apila(int n){
		if (n != Integer.MIN_VALUE){   // Si el parametro es incorrecto no hace nada
			pila.push(n);
			cp++;
		}
	}
	
	/**
	 * Apila la posicion 'd' desde la memoria de datos
	 * @param d
	 */
	private void apilaDir(int d){
		if (d != Integer.MIN_VALUE){   // Si el parametro es incorrecto no hace nada
			// Comprueba que exista posicion y que este inicializada
			if (d < 0)
				setError("Posicion de Memoria de Datos no valida: " + d);
			else if (mDatos.size() > d && mDatos.get(d) != null){
				pila.push(mDatos.get(d));
				cp++;
			}
			else 
				setError("Posicion de Memoria de Datos \"" + d + "@\" no inicializada");
		}
	}
	
	/**
	 * Guarda la cima de la pila en la posicion de memoria 'd' y desapila
	 * @param d
	 */
	private void desapilaDir(int d){
		if (d != Integer.MIN_VALUE){   // Si el parametro es incorrecto no hace nada
			if (d < 0)
				setError("Posicion de Memoria de Datos no valida: " + d);
			else if (!pila.empty()){
				if (mDatos.size() < d+1)
					for (int i=mDatos.size(); i<d+1; i++)
						mDatos.add(null);
				mDatos.set(d, pila.peek());
				pila.pop();
				cp++;
			}
			else 
				setError("Pila vacia!!!");
		}
	}
	
	/**
	 * suma las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void suma(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			op1 = op0 + op1;
			pila.push(op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * resta las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void resta(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			op1 = op0 - op1;
			pila.push(op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * multiplica las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void mult(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			op1 = op0 * op1;
			pila.push(op1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * divide las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void div(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 != 0){
				op1 = op0 / op1;
				pila.push(op1);
				cp++;
			}
			else
				setError("Division por 0!!!");
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula el resto de la division entera de las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void mod(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 != 0){
				op1 = op0 % op1;
				pila.push(op1);
				cp++;
			}
			else
				setError("Division por 0!!!");
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula el inverso de un numero
	 */
	private void inverso(){
		if (!pila.empty()){
			int op = pila.peek();   
			if (op != 0){
				pila.pop();
				pila.push(-op);
			}
			cp++;
		}
		else 
			setError("Falta operando en la pila");
	}
	
	/**
	 * calcula la or de las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void or(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 == 0 && op0 == 0)
			    pila.push(0);
			else
				pila.push(1);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula la and de las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void and(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 == 1 && op0 == 1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
		
	/**
	 * mira si son iguales las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void igual(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 == op0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * mira si son distintos las dos posiciones mas altas de la pila
	 * Primer operando subcima, segundo operando cima.
	 */
	private void dist(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op1 != op0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula si la subcima es menor que la cima
	 */
	private void menor(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op0 < op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula si la subcima es menor o igual que la cima
	 */
	private void menigual(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op0 <= op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula si la subcima es mayor que la cima
	 */
	private void mayor(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op0 > op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula si la subcima es mayor o igual que la cima
	 */
	private void mayigual(){
		if (pila.size() >= 2){
			int op1 = pila.peek();       
			pila.pop();
			int op0 = pila.peek();
			pila.pop();
			if (op0 >= op1)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * calcula la negacion de la cima
	 */
	private void neg(){
		if (!pila.empty()){
			int op = pila.peek();       
			pila.pop();
			if (op == 0)
			    pila.push(1);
			else
				pila.push(0);
			cp++;
		}
		else 
			setError("Faltan operandos en la pila");
	}
	
	/**
	 * imprime la informacion del estado actual de la maquina p
	 * @return lo mismo que imprime por pantalla
	 */
	public String imprimeEstado(){
		String sout = "-------------------------- ESTADO --------------------------\n";
		sout += "CP = " + Integer.toString(cp) + "\t\t F = " + f + "\n";
		sout += "Memoria Datos: " + mDatos + "\n";
		sout += "Memoria de Instrucciones: " + mInstr + "\n";
		sout += "Pila: " + pila + "\n";
		if (f == 'e')
			sout += "ERROR: " + info + "\n";
		sout += "------------------------------------------------------------\n";
		System.out.println(sout);
		return sout;
	}
	
}
